/**
 * 封装axios请求, 需要与后端协调好规范
 *
 */

import axios, { ResponseType } from "axios";
import { ElMessage, ElMessageBox } from "element-plus";
import Global from "@/store";
import { getCookie } from "./cookie";
import { logOut } from "@/api/user";

/**
 * 基础请求
 * 1. 会在url的基础上加/api前缀, 代理的时候带/api的会被代理到测试ip
 * 2. 第三个参数对于GET请求是params, 对于POST请求是data, POST请求要加params在第二个参数里面手动加
 * 3. POST默认json, 如果需要用form, 在第四个参数加header: { codeMode: 'form' }, 会自动进行转换
 * @param method 请求方法
 * @param path 请求路径
 * @param data 请求数据
 * @param options `axios`请求配置，优先级最高
 */
export default function request<T = any>(method: "GET" | "POST" | "DELETE" | "PUT", path: string, data?: any, options: Partial<RequestOptions> = {}) {
  path = "/api" + path;
  const header =
    !options || !options.headers
      ? {
          codeMode: "json",
        }
      : {};
  return service({
    method: method,
    url: path,
    params: method === "GET" ? data : {},
    data: method !== "GET" ? data : {},
    // dataType: 'json', 默认就是，可写可不写，根据情况定
    headers: header,
    ...options,
    timeout: options.timeout,
  }) as unknown as Promise<ApiResult<T>>;
}

/** axios实例 */
const service = axios.create({
  baseURL: "",
  timeout: Global.config.requestOvertime,
});

/**
 * 请求拦截
 * 1. 处理POST请求 json | form 传参的header设置
 * 2. 将token设置到Authorization
 */
service.interceptors.request.use(
  async (options) => {
    const postMode = {
      json: "application/json",
      form: "application/x-www-form-urlencoded; charset=UTF-8",
    };
    /**
     * 处理`json` | 表单 传参的header.Content-Type转换
     */
    if (options.method!.toUpperCase() === "POST") {
      const key = options.headers!["codeMode"] as "json" | "form";
      if (key && postMode[key]) {
        options.headers!["Content-Type"] = postMode[key];
      }
    }

    /**
     * 设置token到Authrization
     */
    if (!options.headers!["Authorization"]) {
      options.headers!["Authorization"] = "Bearer " + getCookie(Global.config.tokenKey);
    }
    return options;
  },
  (error) => {
    return Promise.reject(error);
  }
);

/**
 * 响应拦截
 * 1. 返回res.data
 * 2. 处理后端返回文件(数据流)的情况
 * 2. 根据res.status和res.data.code处理异常情况(给消息提示)
 * 3. 对请求异常的做处理, 返回code=-1
 */
service.interceptors.response.use(
  (res) => {
    let result = res.data;
    /** 如果是文件 */
    if (["arraybuffer", "blob"].includes(res.config.responseType!)) {
      result = {
        code: 1,
        data: res.data,
      };
    }
    responseTip(res.status, result, (<any>res.config).showTip);
    return result;
  },
  (error) => {
    const tip = `${error}`;
    const sign = "Request failed with status code";
    if (tip.includes(sign)) {
      const tips = tip.split(sign);
      responseTip(parseInt(tips[1]), {
        code: -1,
        msg: error.response && error.response.data ? error.response.data.msg : "",
      });
    } else if (tip.includes("timeout")) {
      ElMessage.warning(`网络响应超时了`);
    } else if (tip.includes("Network Error")) {
      ElMessage.warning(`网络出错了`);
    } else {
      ElMessage.warning(`${error}`);
      console.warn("响应拦截器异常处理 >>", error);
    }
    // 这样其他地方调用时，就一定会有返回值，就不用写多一个`catch`来捕获失败状态了
    return {
      code: -1,
      msg: `${error}`,
      data: {},
    };
  }
);

/**
 * 响应异常提示
 * @param status 状态码
 * @param info res.data
 * @param showTip
 */
function responseTip(status: number, info: ResponseInfo, showTip?: string | boolean) {
  switch (status) {
    case 404:
      ElMessage.error(`接口不存在`);
      break;

    case 401:
      ElMessage.error(`登陆已过期，请重新登陆`);
      !Global.config.isDev && (window.location.href = Global.config.outsideUrlInfo.login);
      Global.user.resetInfo();
      break;

    case 400:
      ElMessage.error(`请求的参数有误`);
      break;

    case 301:
    case 302:
      ElMessage.info(`请求已被重定向`);
      break;

    case 200:
      if (info.code !== 1) {
        switch (info.code) {
          case 0:
            ElMessage.error(`登录已过期，请重新登陆`);
            !Global.config.isDev && (window.location.href = Global.config.outsideUrlInfo.login);
            Global.user.resetInfo();
            break;

          case -2:
            ElMessageBox.alert(info.msg || "当前账号没有角色信息，请找运营人员添加角色，点击确定跳转登录页面", "操作提示", {
              confirmButtonText: "确定",
              callback() {
                logOut();
                !Global.config.isDev && (window.location.href = Global.config.outsideUrlInfo.login);
              },
            });
            break;

          case 204:
            if (showTip) ElMessage.error("登录时效过期了");
            break;

          default:
            if (showTip) ElMessage.error(typeof showTip === "boolean" ? info.msg || "操作失败" : showTip);
            break;
        }
      }
      break;
  }
  if (status >= 500) {
    /** 只在开发环境下提示 */
    Global.config.isDev && ElMessage.error(info.msg || `服务器出错了`);
  }
}

/**
 * 局部类型
 */
interface RequestOptions {
  /**
   * 请求头配置，会覆盖默认设置
   * - 默认`headers: { "codeMode": "json" }`;
   * - 需要表单形式就`headers: { "codeMode": "form" }`;
   * - 两者都不要就`headers: { "codeMode": "" }`;
   * - 其他头部设置自行定义
   */
  headers: {
    /**
     * 默认传参类型
     * | 值 | 说明 |
     * |---|---|
     * | json | json格式数据 |
     * | form | 表单格式 |
     * | 空字符串 | 上传文件需要用到 |
     */
    codeMode?: "json" | "form" | "";
  } & BaseObj<string>;
  /**
   * 请求数据类型
   */
  dataType: "json" | "text" | "";
  /** 接口数据响应类型 */
  responseType: ResponseType;
  /** 是否在`res.code !== 1`的时候显示提示，默认`false`，传入`true`则用`res.msg`作为提示，也可以传入字符串作为提示内容 */
  showTip: boolean | string;
  /** 单独设置超时时间，毫秒 */
  timeout: number;
}

/** 响应类型 */
interface ResponseInfo {
  msg?: string;
  code: number;
}
